import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
import math

# ============================================================
# CNVS Monte Carlo — Test 6: Hybrid Adversarial Stress Test
# Semantic Inference + Thermodynamic Shannon Guessing
# ============================================================

rng = np.random.default_rng(20260525)

def build_fragment_pool(I_G_bits: int, I_0_bits: int, lambda_factor: int) -> np.ndarray:
    if I_G_bits <= 0 or I_0_bits <= 0:
        raise ValueError("I_G_bits and I_0_bits must be positive.")
    if lambda_factor < 1:
        raise ValueError("lambda_factor must be an integer >= 1.")

    # Uso rigorously math.ceil
    m_fragments = math.ceil(I_G_bits / I_0_bits)
    return np.repeat(np.arange(m_fragments), lambda_factor)

def run_cnvs_hybrid_test(
    I_G_bits: int,
    I_0_bits: int,
    lambda_factor: int = 2,
    q_values: np.ndarray = None,
    trials: int = 3000,
    rho_c: float = 0.01
) -> pd.DataFrame:
    
    if q_values is None:
        q_values = np.linspace(0.01, 0.99, 60)

    pool = build_fragment_pool(I_G_bits, I_0_bits, lambda_factor)
    n_total = len(pool)
    m_fragments = len(np.unique(pool))

    results = []

    for q in q_values:
        k_captured = int(round(q * n_total))
        wins = 0
        
        H_res_values = []
        p_guess_values = []

        for _ in range(trials):
            # 1. PHYSICAL CAPTURE (Estrazione Fisica Pura)
            captured = rng.choice(pool, size=k_captured, replace=False)
            unique_captured = len(np.unique(captured))
            missing_physical = m_fragments - unique_captured

            # 2. SEMANTIC INFERENCE (Contagio logico)
            p_infer = 1.0 - (1.0 - rho_c) ** unique_captured
            
            inferred = 0
            if missing_physical > 0:
                inferred = rng.binomial(missing_physical, p_infer)

            # Quanti frammenti mancano DAVVERO all'appello ora?
            absolutely_missing = missing_physical - inferred

            # 3. WINNING CONDITION & SHANNON ENTROPY
            if absolutely_missing == 0:
                # L'attaccante ha preso o dedotto tutto. Vince.
                wins += 1
                H_res_values.append(0)
                p_guess_values.append(1.0)
            else:
                # 4. THERMODYNAMIC GUESS (Forza bruta sull'entropia residua)
                H_res = absolutely_missing * I_0_bits
                # Protezione Underflow: se H_res > 1024 bit, p_guess è zero spaccato
                p_guess = 0.0 if H_res > 1024 else 2.0 ** (-H_res)
                
                if rng.random() < p_guess:
                    wins += 1
                    
                H_res_values.append(H_res)
                p_guess_values.append(p_guess)

        results.append({
            "I_G_bits": I_G_bits,
            "I_0_bits": I_0_bits,
            "lambda_factor": lambda_factor,
            "rho_c": rho_c,
            "q": q,
            "M_fragments": m_fragments,
            "N_total_nodes": n_total,
            "P_win": wins / trials,
            "mean_H_res": np.mean(H_res_values),
            "mean_p_guess": np.mean(p_guess_values),
        })

    return pd.DataFrame(results)

def plot_hybrid_results(df: pd.DataFrame, title: str):
    plt.figure(figsize=(11, 7))

    for label, group in df.groupby("label", sort=False):
        plt.plot(group["q"], group["P_win"], linewidth=2.5, label=label)

    plt.axvline(x=1/3, color="black", linestyle="--", alpha=0.7, label="BFT Limit (1/3)")

    plt.xlabel("Fraction of Physically Corrupted Nodes (q)", fontsize=12)
    plt.ylabel("Probability of System Collapse (False Positive)", fontsize=12)
    plt.title(title, fontsize=14, fontweight='bold')
    plt.grid(True, linestyle=":", alpha=0.7)
    plt.legend(loc='upper left', fontsize=10)
    plt.tight_layout()
    plt.savefig("CNVS_Test_6_Hybrid_Model.png", dpi=200)
    plt.show()

# ============================================================
# EXECUTION (Ceteris Paribus per dimostrare la scalabilità)
# ============================================================

# Manteniamo I_G fisso e facciamo scalare correttamente rho_c con I_0
scenarios = [
    {"label": "Phase 1: Low Granularity (I0=100, \u03C1c=0.05)", "I_G": 2000, "I_0": 100, "rho_c": 0.05, "color": "red"},
    {"label": "Phase 2: Medium Granularity (I0=50, \u03C1c=0.02)", "I_G": 2000, "I_0": 50,  "rho_c": 0.02, "color": "orange"},
    {"label": "Phase 3: High Granularity (I0=10, \u03C1c=0.005)", "I_G": 2000, "I_0": 10,  "rho_c": 0.005, "color": "blue"},
    {"label": "Phase 4: CNVS Pulverization (I0=2, \u03C1c=0.001)", "I_G": 2000, "I_0": 2,   "rho_c": 0.001, "color": "green"},
]

print("Running Hybrid Adversarial Stress Test...")

all_results = []
for sc in scenarios:
    df = run_cnvs_hybrid_test(
        I_G_bits=sc["I_G"],
        I_0_bits=sc["I_0"],
        lambda_factor=2,  # Ottimizzato
        rho_c=sc["rho_c"],
        trials=3000,
        q_values=np.linspace(0.01, 0.99, 60),
    )
    df["label"] = sc["label"]
    all_results.append(df)

results = pd.concat(all_results, ignore_index=True)

plot_hybrid_results(results, "CNVS Test 6: Hybrid Adversarial Model (Semantic + Thermodynamic)")

results.to_csv("cnvs_test_6_hybrid_results.csv", index=False)
print("Execution Complete. Results saved to CSV.")